defmodule SockWorkstation.OpusDecoderGen do
  use GenServer
  require Logger

  alias SockWorkstation.{CoopMgrGen, SockCoopClient}
  # alias SockWorkstation.FFmpegPlayer

  @name __MODULE__
  def start_link({session_id, sent_pid}) do
    GenServer.start_link(@name, {session_id, sent_pid}, name: via_tuple(session_id, "decoder"))
  end

  @impl true
  def init({session_id, sent_pid}) do
    IO.puts("OpusDecoderGen started: #{inspect(session_id)}, #{inspect(sent_pid)}")
    # 解码器进程
    decoder_port =
      Port.open({:spawn_executable, priv_path("opus_decoder")}, [:binary, :use_stdio])

    CoopMgrGen.fetch_topcm_pid(session_id, self())
    # {:ok, ffmpeg_pid} = FFmpegPlayer.start_link()
    # CtrlMgrGen.add_decoder({self(), decoder_port})
    {:ok, %{decoder_port: decoder_port, session_id: session_id, sent_pid: sent_pid}}
  end

  defp priv_path(file) do
    Path.join(:code.priv_dir(:sock_workstation), file)
  end

  @impl true
  def handle_info({decoder_port, {:data, data}}, %{decoder_port: decoder_port} = state) do
    # Logger.info("从 C 程序接收 PCM 数据: #{inspect(data)}")
    File.write!("audio.pcm", data, [:append])
    # send(state.sent_pid, {:pcm, data})
    {:noreply, state}
  end

  def opus_to_pcm(session_id, opus_data) do
    GenServer.cast(via_tuple(session_id, "decoder"), {:send_decode_opus, opus_data})
  end

  # def opus_to_pcm(pid, opus_data) do
  #   GenServer.cast(pid, {:send_decoder_opus, opus_data})
  # end

  @impl true
  def handle_cast({:send_decode_opus, opus_data}, %{decoder_port: decoder_port} = state) do
    try do
      Port.command(decoder_port, opus_data)
    rescue
      error ->
        IO.inspect(error, label: "Port command failed")
        raise error
    end

    {:noreply, state}
  end

  @impl true
  def terminate(_reason, state) do
    # IO.puts("OpusDecoderGenServer is terminating. Reason: #{inspect(reason)}")
    # CoopMgrGen.rm_to_pcm_pid(state.session_id)
    Port.close(state.decoder_port)

    # IO.puts("Cleaning up resources: #{inspect(state.resources)}")
    # 在这里执行清理操作，例如关闭文件、释放资源等
    # FFmpegPlayer.stop(SockWorkstation.CtrlMgrGen.get_state().ffmpeg_pid)
    :ok
  end

  defp via_tuple(session_id, type) do
    {:via, Registry, {SockWorkstation.SessionRegistry, {session_id, type}}}
  end
end

defmodule SockWorkstation.PcmEncoderGen do
  use GenServer
  require Logger

  alias SockWorkstation.CoopMgrGen
  alias SockWorkstation.{SockCoopClient}

  @name __MODULE__

  def start_link(session_id) do
    GenServer.start_link(@name, session_id, name: via_tuple(session_id, "encoder"))
  end

  @impl true
  def init(session_id) do
    self_pid = self()
    # 编码器进程
    encoder_port =
      Port.open({:spawn_executable, priv_path("opus_encoder")}, [:binary, :exit_status])

    CoopMgrGen.fetch_topus_pid(session_id, self_pid)

    # CtrlMgrGen.fetch_encoder({self(), encoder_port})
    IO.puts("PcmEncoderGen started: #{inspect(session_id)}")
    {:ok, %{encoder_port: encoder_port, session_id: session_id, coop_pid: nil}}
  end

  def get_state(session_id) do
    GenServer.call(via_tuple(session_id, "encoder"), :get_state)
  end

  def fetch_coop_pid(session_id, pid) do
    GenServer.cast(via_tuple(session_id, "encoder"), {:fetch_coop_pid, pid})
  end

  # def rm_coop_pid(session_id) do
  #   GenServer.cast(via_tuple(session_id, "encoder"), :rm_coop_pid)
  # end

  defp priv_path(file) do
    Path.join(:code.priv_dir(:sock_workstation), file)
  end

  @impl true
  def handle_call(:get_state, _from, state) do
    {:reply, state, state}
  end

  @impl true
  def handle_cast({:fetch_coop_pid, coop_pid}, state) when coop_pid == state.coop_pid do
    {:noreply, state}
  end

  def handle_cast({:fetch_coop_pid, pid}, state) do
    {:noreply, %{state | coop_pid: pid}}
  end

  # def handle_cast(:rm_coop_pid, state) do
  #   {:noreply, %{state | coop_pid: nil}}
  # end

  def handle_cast({:send_encode_pcm, pcm_data}, %{encoder_port: encoder_port} = state) do
    Port.command(encoder_port, pcm_data)
    {:noreply, state}
  end

  @impl true
  def handle_info(
        {encoder_port, {:data, data}},
        %{encoder_port: encoder_port, coop_pid: coop_pid} = state
      ) do
    # Logger.info("从 C 程序接收 opus v3 数据: #{inspect(data)}")
    with :ok <- SockCoopClient.send_binary(coop_pid, data) do
      {:noreply, state}
    end
  end

  def pcm_to_opus(session_id, pcm_data) do
    GenServer.cast(via_tuple(session_id, "encoder"), {:send_encode_pcm, pcm_data})
  end

  @impl true
  def terminate(_reason, state) do
    # IO.puts("PcmEncoderGenServer is terminating. Reason: #{inspect(reason)}")
    Port.close(state.encoder_port)
    # CoopMgrGen.rm_to_opus_pid(state.session_id)
    :ok
  end

  defp via_tuple(session_id, type) do
    {:via, Registry, {SockWorkstation.SessionRegistry, {session_id, type}}}
  end
end

defmodule PCMToWAV do
  # 16 kHz
  @sample_rate 16000
  # 单声道
  @channels 1
  # 16 位深度
  @bits_per_sample 16
  @byte_rate @sample_rate * @channels * div(@bits_per_sample, 8)
  @block_align @channels * div(@bits_per_sample, 8)

  def save_as_wav(pcm_data, file_path) do
    # 文件头大小为 44 字节，减去 8 字节的 RIFF 和 WAVE 头部
    file_size = byte_size(pcm_data) + 36
    header = build_wav_header(file_size)

    File.write!(file_path, header <> pcm_data)
  end

  defp build_wav_header(file_size) do
    <<
      # Chunk ID
      "RIFF",
      # Chunk Size
      file_size::little-32,
      # Format
      "WAVE",
      # Subchunk1 ID
      "fmt ",
      # Subchunk1 Size (16 for PCM)
      16::little-32,
      # Audio Format (1 for PCM)
      1::little-16,
      # Number of Channels
      @channels::little-16,
      # Sample Rate
      @sample_rate::little-32,
      # Byte Rate
      @byte_rate::little-32,
      # Block Align
      @block_align::little-16,
      # Bits per Sample
      @bits_per_sample::little-16,
      # Subchunk2 ID
      "data",
      # Subchunk2 Size (PCM data size)
      file_size::little-32
    >>
  end
end

# {:ok,ff_pid} = FFmpegPlayer.start_link()
# IO.puts("OpusState.ffmpeg_pid: #{OpusState.ffmpeg_pid}")
# IO.inspect(OpusState.ffmpeg_pid)
# SockWorkstation.send_binary(CtrlMgrGen.get_state.socket_pid, data)
# send(CtrlMgrGen.get_state.encoder_pid, {:pcm_data, data})

# data
# |> Stream.each(&decode_port.command(p, &1))
# |> Stream.run()

# case File.open("audio.pcm", [:append, :binary]) do
#   {:ok, file} ->

#     IO.binwrite(file, data)
#     # Process.sleep(100)
#     File.close(file)
#     # {:reply, :ok, {:binary, <<0x00>>}, state}
#   # IO.puts("Opus data successfully appended  ")
#   {:ok, state}
#   {:error, reason} ->
#     IO.puts("Failed to open file: #{inspect(reason)}")
# {:ok, state}
# end

#    p =
#   decode_port.open({:spawn, "ffplay -f s16le -ar 16000 -ac 1 audio.pcm"}, [:binary])

# decode_port.command(p, data)

# {:reply,  data, state}
# PCMToWAV.save_as_wav(data, "output.wav")
